package com.infoauto.opc.gateway.messages.util.serialize.support;


import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.infoauto.opc.gateway.messages.util.StringUtils;
import com.infoauto.opc.gateway.messages.util.serialize.ISerializer;
import com.infoauto.opc.gateway.messages.util.serialize.SerializeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class JSONSerializer implements ISerializer {
    private static final Logger logger = LoggerFactory.getLogger(JSONSerializer.class);

    private static final JSONSerializer DEFAULT_INSTANCE = new JSONSerializer();

    private static ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, true);
        objectMapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, true);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    }

    private String charset = "utf-8";

    private boolean slowSerializeHappened = false;

    public static final JSONSerializer getDefaultInstance() {
        return DEFAULT_INSTANCE;
    }

    public byte[] serialize(Object o) throws SerializeException {
        if (o == null) return new byte[0];

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer writer = null;
        try {
            writer = new OutputStreamWriter(baos, charset);
            objectMapper.writeValue(writer, o);
            byte[] data = baos.toByteArray();
            return data;
        } catch (Exception e) {
            throw new SerializeException(e.getMessage(), e);
        } finally {
            safeClose(writer);
            safeClose(baos);
        }
    }

    public <T> T deserialize(byte[] data, Class<T> tpl) throws SerializeException {
        if (data == null || data.length == 0) return null;

        Object object = null;
        String s = null;

        try {
            s = new String(data, charset);
        } catch (UnsupportedEncodingException e) {
            throw new SerializeException(e.getMessage(), e);
        }

        try {
            object = objectMapper.readValue(s, TypeFactory.rawClass(tpl));
        } catch (Exception e) {
            throw new SerializeException("error while parsing json character -> " + s.replaceAll("\\r\\n", ""), e);
        }

        return (T) object;
    }

    public <T> List<T>  deserializeList(byte[] data, Class<?> elementClasses) throws SerializeException {
        if (data == null || data.length == 0) return null;

        List<T> object = null;
        String s = null;

        try {
            s = new String(data, charset);
        } catch (UnsupportedEncodingException e) {
            throw new SerializeException(e.getMessage(), e);
        }

        try {
            JavaType javaType = getCollectionType(ArrayList.class, elementClasses);
            object = (List<T>)objectMapper.readValue(s, javaType);
        } catch (Exception e) {
            throw new SerializeException("error while parsing json character -> " + s.replaceAll("\\r\\n", ""), e);
        }

        return  object;
    }

    public <T> T deserialize(String json, Class<T> tpl) throws SerializeException {
        if (!StringUtils.hasText(json)) {
            return null;
        }
        try {
            return deserialize(json.getBytes(charset), tpl);
        } catch (UnsupportedEncodingException e) {
            throw new SerializeException(e.getMessage(), e);
        }
    }

    public <T> T deserialize(String json, TypeReferenceWrapper<T> tpr) throws SerializeException {
        if (!StringUtils.hasText(json)) {
            return null;
        }
        try {
            return objectMapper.readValue(json, tpr.getTypeReferenceForJackson());
        } catch (UnsupportedEncodingException e) {
            throw new SerializeException(e.getMessage(), e);
        } catch (JsonParseException e) {
            throw new SerializeException(e.getMessage(), e);
        } catch (JsonMappingException e) {
            throw new SerializeException(e.getMessage(), e);
        } catch (IOException e) {
            throw new SerializeException(e.getMessage(), e);
        }
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public String serialize2JSON(Object o) throws SerializeException {
        try {
            return new String(this.serialize(o), this.charset);
        } catch (UnsupportedEncodingException e) {
            throw new SerializeException(e.getMessage(), e);
        }
    }

    private void safeClose(Closeable target) {
        if (target != null) {
            try {
                target.close();
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    public static JavaType getCollectionType(Class<?> collectionClass, Class<?> elementClasses) {
        return objectMapper.getTypeFactory().constructParametricType( TypeFactory.rawClass(collectionClass),
                TypeFactory.rawClass(elementClasses));
    }
}
